package pkg

import (
	"bytes"
	"context"
	"fmt"
	"github.com/SkyAPM/go2sky"
	"github.com/sirupsen/logrus"
	"os"
	"path/filepath"
	"runtime"
	"strings"
	"time"
)

type LoggerPkg struct {
	*logrus.Logger
	preLogFunc PreLogFunc
}

type Entry struct {
	*logrus.Entry
}

type PreLogFunc func(title string, content string) (string, string)

var Logger *LoggerPkg

func init() {
	Logger = NewLogger("", nil, true)
}

func NewLogger(logDir string, preLogFunc PreLogFunc, log2Tracer bool) *LoggerPkg {
	logger := logrus.New()
	logger.SetReportCaller(false) // 禁用默认的Caller功能，为了通过自定义封装logrus的日志记录方法后，能正常获取写日志的原始文件位置信息
	logger.SetFormatter(&CustomLogFormatter{})
	customLogHook := &CustomLogHook{LogDir: logDir, Log2Tracer: log2Tracer, logger: logger}
	customLogHook.createLogFileIfNeeded() // 初始化时创建日志文件
	logger.AddHook(customLogHook)
	return &LoggerPkg{Logger: logger, preLogFunc: preLogFunc}
}

func (p *LoggerPkg) WithContext(ctx context.Context) *Entry {
	return &Entry{p.Logger.WithContext(ctx)}
}

func (e *Entry) Info(args ...interface{}) {
	e.logWithCustomLogic(logrus.InfoLevel, args...)
}

func (e *Entry) Error(args ...interface{}) {
	e.logWithCustomLogic(logrus.ErrorLevel, args...)
}

func (e *Entry) Warn(args ...interface{}) {
	e.logWithCustomLogic(logrus.WarnLevel, args...)
}

func (e *Entry) Panic(args ...interface{}) {
	e.logWithCustomLogic(logrus.PanicLevel, args...)
}

func (e *Entry) Fatal(args ...interface{}) {
	e.logWithCustomLogic(logrus.FatalLevel, args...)
}

func (e *Entry) Debug(args ...interface{}) {
	e.logWithCustomLogic(logrus.DebugLevel, args...)
}

func (e *Entry) Trace(args ...interface{}) {
	e.logWithCustomLogic(logrus.TraceLevel, args...)
}

func (e *Entry) logWithCustomLogic(level logrus.Level, args ...interface{}) {
	var title, content string

	// 解析标题和内容
	if len(args) > 0 {
		if t, ok := args[0].(string); ok {
			title = t
		}
	}
	if len(args) > 1 {
		if c, ok := args[1].(string); ok {
			content = c
		}
	}

	// 调用前置处理函数
	if Logger.preLogFunc != nil {
		title, content = Logger.preLogFunc(title, content)
	}

	// 设置字段
	fields := logrus.Fields{
		"content": content,
	}

	entry := e.Entry.WithFields(fields)

	// 设置调用者信息
	if caller := getCaller(); caller != nil {
		entry = entry.WithField("caller", fmt.Sprintf("%s:%d %s", caller.File, caller.Line, caller.Function))
	}

	// 记录日志
	entry.Log(level, title)
}

func getCaller() *runtime.Frame {
	pcs := make([]uintptr, 10)
	n := runtime.Callers(3, pcs)
	frames := runtime.CallersFrames(pcs[:n])
	for {
		frame, more := frames.Next()
		if !more {
			break
		}
		// 排除logWithCustomLogic方法本身和log包的方法调用
		if !strings.Contains(frame.Function, "pkg.") && !strings.Contains(frame.Function, "logrus.") {
			return &frame
		}
	}
	return nil
}

type CustomLogFormatter struct{}

func (c *CustomLogFormatter) Format(entry *logrus.Entry) ([]byte, error) {
	var b *bytes.Buffer
	if entry.Buffer != nil {
		b = entry.Buffer
	} else {
		b = &bytes.Buffer{}
	}

	timestamp := entry.Time.Format("2006-01-02 15:04:05")
	var newLog string

	caller, ok := entry.Data["caller"].(string)
	if !ok {
		caller = "unknown"
	}

	newLog = fmt.Sprintf("[%s] [%s] [%s] [%s] [%s] [%s]\n",
		timestamp, entry.Level, caller,
		entry.Data["trace_id"], entry.Message, entry.Data["content"])

	b.WriteString(newLog)
	return b.Bytes(), nil
}

type CustomLogHook struct {
	LogDir         string
	Log2Tracer     bool
	currentLogFile *os.File
	logger         *logrus.Logger
}

func (hook *CustomLogHook) Levels() []logrus.Level {
	return logrus.AllLevels
}

func (hook *CustomLogHook) Fire(entry *logrus.Entry) error {
	if entry.Context != nil {
		var traceID string
		_, existed := GCM.GetContext(entry.Context)
		if existed {
			if hook.Log2Tracer {
				span, _, err := Tracer.CreateLocalSpan(entry.Context, LocalSpanOptions{
					OperationName: fmt.Sprintf("log[%s]: %s", entry.Level.String(), entry.Message),
				})
				if err == nil {
					defer span.End()
					span.Tag("log.title", entry.Message)
					if entry.Data["content"] != "" {
						span.Log(time.Now(), entry.Data["content"].(string))
					}
				}
			}
			traceID = go2sky.TraceID(entry.Context)
		}
		entry.Data["trace_id"] = traceID
	}

	// 检查并创建新的日志目录和文件
	hook.createLogFileIfNeeded()

	return nil
}

func (hook *CustomLogHook) createLogFileIfNeeded() {
	now := time.Now()
	logDir := hook.LogDir
	if logDir == "" {
		dir, _ := os.Getwd()
		logDir = filepath.Join(dir, "log")
	}
	path := filepath.Join(logDir, now.Format("200601"))
	if _, err := os.Stat(path); os.IsNotExist(err) {
		if makeErr := os.MkdirAll(path, os.ModePerm); makeErr != nil {
			hook.logger.Error("failed to create log path: ", path, " error: ", makeErr)
			return
		}
	}
	logName := fmt.Sprintf("%s_%s", "log", now.Format("20060102"))
	logFilePath := filepath.Join(path, fmt.Sprintf("%s.log", logName))

	// 如果当前日志文件已经存在，则不重新创建
	if hook.currentLogFile != nil && hook.currentLogFile.Name() == logFilePath {
		return
	}

	// 创建新的日志文件
	logFile, err := os.OpenFile(logFilePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
	if err != nil {
		hook.logger.Error("failed to open the log file: ", err)
		return
	}

	// 更新 logger 输出和当前日志文件
	hook.logger.SetOutput(logFile)
	hook.currentLogFile = logFile
}
